// QuicksView.cpp : implementation of the CQuicksView class
//
/////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "resource.h"
#include <sstream>
#include <windows.h>

#include "QuicksView.h"

CQuicksView::CQuicksView() 
	: m_totalLength(0), 
	m_resultsLength(0), 
	m_init(false), 
	m_updating(false),
	m_lastUserInput(_TEXT("")) {
}

int CQuicksView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	SetMsgHandled(false);
	SetEventMask(ENM_PROTECTED);
	RegisterHotKey(m_hWnd, 100, MOD_WIN, 'C');
	Clear();
	RefreshResultsDisplay();
	::ShowWindow(GetParent(), SW_HIDE);
	return 0;
}


void CQuicksView::OnDestroy() {
	UnregisterHotKey(m_hWnd, 100);
}

void CQuicksView::OnHotKey(int nHotKeyID, UINT uModifiers, UINT uVirtKey) {
	Clear();
	RefreshResultsDisplay(true);
	::ShowWindow(GetParent(), SW_SHOW);
	::SetForegroundWindow(GetParent());
	SwitchToThisWindow(m_hWnd, true);
}


BOOL CQuicksView::PreTranslateMessage(MSG* pMsg)
{
	pMsg;
	return FALSE;
}

void CQuicksView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	SetMsgHandled(false);
	std::wstringstream ss;
	switch(nChar)
	{
	case VK_RETURN:
		if (m_matchResult.matches.size() > 0) {
			m_wm.SwitchTo(*m_matchResult.matches[0].data);
			::ShowWindow(GetParent(), SW_HIDE);
			Clear();
			SetMsgHandled(true);
		}
		break;
	case VK_BACK:

		break;
	case VK_ESCAPE:
		{
			CHARRANGE crUser;
			GetUserSel(crUser);
			if (crUser.cpMin == 0 && crUser.cpMax == 0)
				::ShowWindow(GetParent(), SW_HIDE);
			Clear();
		}
		break;
	case VK_TAB:
		{
			SetMsgHandled(true);
			if (m_matchResult.hint.size() > 0) {
				m_updating = true;
				CHARRANGE crUser;
				GetUserSel(crUser);
				SetSel(crUser);
				int nTextLen = GetSelText((LPTSTR)m_text);
				std::wstring strUser(m_text);
				strUser += m_matchResult.hint;
				ReplaceSel(strUser.c_str(), false);
				crUser.cpMin = crUser.cpMax = strUser.size();
				SetSel(crUser);
				RefreshResultsDisplay();
				m_updating = false;
			}
		}
		break;
	default:
		break;

	}
	::SendMessage(m_hWnd, ID_REFRESH_RESULTS, NULL, NULL);
}

void CQuicksView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	m_updating = true;
	SetMsgHandled(false);
	m_updating = false;
}

void CQuicksView::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	m_updating = true;
	SetMsgHandled(false);
	switch(nChar)
	{
	case VK_BACK:
		break;
	case VK_DELETE:
		break;
	case VK_RETURN:
		SetMsgHandled(true);
		break;
	case VK_TAB:
		//SetMsgHandled(true);
		break;
	default:
		break;
	}
	//RefreshResultsDisplay();
}
	
void CQuicksView::RefreshResultsDisplay(bool force) {
	CHARRANGE crCur;
	CHARRANGE crUser;
	GetSel(crCur);
	GetUserSel(crUser);
	SetSel(crUser);
	int nTextLen = GetSelText((LPTSTR)m_text);
	std::wstring strUser(m_text);
	// don't update if nothing's changed
	if (!force && strUser == m_lastUserInput) {
		SetSel(crCur);
		return;
	}
	const std::vector<WindowManager::WindowDesc>* windows = m_wm.GetWindows();
	std::vector<MatchInput<WindowManager::WindowDesc> > items;
	MatchOutput<WindowManager::WindowDesc> mo;
	m_shownWindows.clear();
	for (std::vector<WindowManager::WindowDesc>::const_iterator it = windows->begin(); it != windows->end(); it++) {
		m_shownWindows.push_back(*it);
		MatchInput<WindowManager::WindowDesc> mi;
		mi.str = &(it->name);
		mi.data = &(*it); // no pointers into std!
		items.push_back(mi);
	}
	m_ic.MatchInput(m_matchResult, strUser, items);
	SetSel(crCur);
	RefreshResults();
	m_lastUserInput = strUser;
}

void CQuicksView::RefreshResults() {
	CHARRANGE crResults;
	CHARRANGE crCur;
	CHARRANGE crUser;
	GetSel(crCur);


	// first unprotect all
	CHARFORMAT2 cf;
	cf.cbSize = sizeof(CHARFORMAT);
	cf.dwMask = CFM_PROTECTED;
	cf.dwEffects = NULL;
	cf.yHeight = 0;
	cf.yOffset = 0;
	cf.crTextColor = RGB(0,0,0);
	cf.bCharSet = NULL;
	cf.bPitchAndFamily = NULL;
	SetCharFormat(cf, SCF_ALL);

	// write the results in
	GetResultSel(crResults);
	std::wstringstream ss;
	
	if (m_matchResult.matches.size() == 0) {
		ss << "[No Matches]"; // constant later
	} else {
		// write the hint, if there is one.
		if (m_matchResult.hint.size() > 0) {
			ss << "[" << m_matchResult.hint << "]";
		}
		ss << "{";
		for (IdoComplete<WindowManager::WindowDesc>::MatchVector::const_iterator it = 
			m_matchResult.matches.begin(); it != m_matchResult.matches.end(); it++) {
				std::wstring name;
				MatchStringToWString(it->str, name);
				if (it == m_matchResult.matches.begin()) {
					ss << name;
				} else {
					ss << " | " << name;
				}
		}
		ss << "}";
	}
	SetSel(crResults);
	ReplaceSel(ss.str().c_str(), false);
	m_resultsLength = ss.str().length();
	m_totalLength = GetTextLength();

	// set protected/unprotected
	GetResultSel(crResults);
	SetSel(crResults);
	cf.dwMask = CFM_PROTECTED | CFM_WEIGHT;
	cf.dwEffects = CFE_PROTECTED;
	cf.wWeight = FW_NORMAL;
	SetCharFormat(cf, SCF_SELECTION);
	GetUserSel(crUser);
	//crUser.cpMax++;
	SetSel(crUser);
	cf.dwEffects = NULL;
	SetCharFormat(cf, SCF_SELECTION);
	
	// need to do the formatting of the results
	COLORREF crMatching = RGB(255,105,180);
	COLORREF crHint = RGB(0,191,255);
	COLORREF crDefault = RGB(255,255,255);
	COLORREF crNone = RGB(255,69,0);
	cf.dwMask = CFM_COLOR | CFM_WEIGHT;
	cf.wWeight = FW_NORMAL;
	CHARRANGE crChar;
	GetResultSel(crChar);
	crChar.cpMax = crChar.cpMin;
	// format the hints first
	if (m_matchResult.hint.size() > 0) {
		cf.crTextColor = crHint; // hint.
		cf.dwEffects = CFE_PROTECTED;
		cf.wWeight = FW_NORMAL;
		crChar.cpMax = crChar.cpMin + m_matchResult.hint.size() + 2;
		SetSel(crChar);
		SetCharFormat(cf, SCF_SELECTION);
	}
	if (m_matchResult.matches.size() == 0) {
		cf.crTextColor = crNone; // no matches. [No Matches].len = 12
		cf.dwEffects = CFE_PROTECTED;
		cf.wWeight = FW_HEAVY;
		SetSel(crChar.cpMin, crChar.cpMin + 12);
		SetCharFormat(cf, SCF_SELECTION);
		// and pretty much done.
	} else {
		crChar.cpMin = crChar.cpMax; // for {
		crChar.cpMax = crChar.cpMin + 1;
		cf.crTextColor = crDefault;
		cf.dwEffects = CFE_PROTECTED;
		cf.wWeight = FW_NORMAL;
		SetSel(crChar);
		SetCharFormat(cf, SCF_SELECTION);

		// now do the actual results
		crChar.cpMin = crChar.cpMax;
		crChar.cpMax = crChar.cpMin;
		bool bMatching = false;
		for (IdoComplete<WindowManager::WindowDesc>::MatchVector::const_iterator it = 
			m_matchResult.matches.begin(); it != m_matchResult.matches.end(); it++) {

				if (it == m_matchResult.matches.begin()) {
					cf.wWeight = FW_HEAVY;
				} else {
					cf.wWeight = FW_NORMAL;
				}
				for (matchstring::const_iterator it2 = it->str.begin(); it2 != it->str.end(); it2++) {
					if (bMatching != it2->matches) {
						if (bMatching) {
							cf.crTextColor = crMatching;
							cf.dwEffects = NULL;
						} else {
							cf.crTextColor = crDefault;
							cf.dwEffects = NULL;
						}
						SetSel(crChar);
						SetCharFormat(cf, SCF_SELECTION);
						crChar.cpMin = crChar.cpMax;
						bMatching = it2->matches;
					}
					crChar.cpMax++;
				}	

				// set the remaining
				if (bMatching) {
					cf.crTextColor = crMatching;
					cf.dwEffects = NULL;
				} else {
					cf.crTextColor = crDefault;
					cf.dwEffects = NULL;
				}
				SetSel(crChar);
				SetCharFormat(cf, SCF_SELECTION);
				crChar.cpMin = crChar.cpMax;
				
				cf.wWeight = FW_NORMAL;
				// now that we're done with that match, need to check if we ended in a match
				// and then set the divider as unmatching
				if (bMatching) {
					cf.crTextColor = crMatching;
					cf.dwEffects = CFE_BOLD;
					SetSel(crChar);
					SetCharFormat(cf, SCF_SELECTION);
					crChar.cpMin = crChar.cpMax;
					bMatching = false;
				}
				// now set the divider as unmatching.
				// if this is the last match, who cares.
				crChar.cpMax += 3; // for " | "
				cf.crTextColor = crDefault;
				cf.dwEffects = NULL;
				SetSel(crChar);
				SetCharFormat(cf, SCF_SELECTION);
				crChar.cpMin = crChar.cpMax;
				bMatching = false;
		}
	}
	
	SetSel(crCur);
	m_updating = false;
}

void CQuicksView::GetResultSel(CHARRANGE& sel) {
	int cLen = GetTextLength();
	sel.cpMin = cLen - m_resultsLength;
	sel.cpMax = cLen;
}

void CQuicksView::GetUserSel(CHARRANGE& sel) {
	int cLen = GetTextLength();
	sel.cpMin = 0;
	sel.cpMax = cLen - m_resultsLength;
}

void CQuicksView::Clear() {
	m_resultsLength = GetTextLength();
	m_matchResult.matches.clear();
	CHARRANGE crCur;
	crCur.cpMin = 0;
	crCur.cpMax = 0;
	SetSel(crCur);
	RefreshResults();
}

LRESULT CQuicksView::OnProtected(int idCtrl, LPNMHDR pnmh, BOOL& bHandled) {
	bHandled = true;
	if (m_updating)
		return false;
	CHARRANGE crCur;
	GetSel(crCur);
	if (crCur.cpMin == 0 && crCur.cpMax == 0)
		return false;
	// set the selection to the end of the user text
	CHARRANGE crUser;
	GetUserSel(crUser);
	crUser.cpMin = crUser.cpMax;
	SetSel(crUser);
	
	return false;
}
LRESULT CQuicksView::OnNextMatch(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
	if (m_matchResult.matches.size() < 2)
		return 0;
	m_updating = true;
	IdoComplete<WindowManager::WindowDesc>::Match m;
	IdoComplete<WindowManager::WindowDesc>::MatchVector::iterator fst = m_matchResult.matches.begin();
	m = *fst;
	m_matchResult.matches.erase(fst);
	m_matchResult.matches.push_back(m);
	RefreshResults();
	bHandled = true;
	m_updating = false;
	return 0;
}

LRESULT CQuicksView::OnPrevMatch(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
	if (m_matchResult.matches.size() < 2)
		return 0;
	m_updating = true;
	IdoComplete<WindowManager::WindowDesc>::Match m;
	IdoComplete<WindowManager::WindowDesc>::MatchVector::iterator lst = m_matchResult.matches.end();
	lst--;
	m = *lst;
	m_matchResult.matches.erase(lst);
	m_matchResult.matches.insert(m_matchResult.matches.begin(), m);
	RefreshResults();
	bHandled = true;
	m_updating = false;
	return 0;
}
LRESULT CQuicksView::OnRefresh(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) {
	RefreshResultsDisplay();
	bHandled = TRUE;
	return 0;
}